import datetime
import math
import os
import sys
import time

# 将工程目录加入包扫描
currPath = os.path.abspath(os.path.dirname(__file__))
srcPath = os.path.split(currPath)[0]
rootPath = os.path.split(srcPath)[0]
projectPath = os.path.split(rootPath)[0]
sys.path.append(projectPath)

from tqdm import tqdm
from src.db.ProdDb import ProdDb
from src.utils import CommonUtils
from src.utils.CommonUtils import cleanScreen
from src.utils.LogUtils import Logger


class CalEarlyWarning:
    def __init__(self, logger, localDb):
        self.logger = logger
        self.localDb = localDb
        self.entTypes = ['T001', 'T002']
        self.reporName = {
            0: "一季报",
            1: "中报",
            2: "三季报",
            3: "年报"
        }
        self.rankNameMapping = {
            0: "低于行业25分位",
            1: "处于行业25-50分位之间",
            2: "处于行业50-75分位之间",
            3: "高于行业75分位"
        }
        # 指定需要格式化单位成医院的指标
        self.unitConvertCodes = set({'ZB_0401', 'ZB_0405', 'ZB_0406'})

    def __loadLatestReportDate__(self, creditCode):
        sql = """
            SELECT `year`, report_type
            FROM fin_index
            WHERE 
                credit_code = %s 
                and index_value IS NOT null
            ORDER BY 
                `year` desc, report_type DESC
            LIMIT 1      
        """
        return self.localDb.QueryOne(sql, (creditCode,))

    def __loadEnterprise__(self, entTypes):
        sql = """
            SELECT
                credit_code,
                ent_type,    
                case when ent_type = 'T002'
                then 'platform'
                else srm_trade_type_superior
                end as industry_code 
            FROM
                ent_enterprise
            WHERE
                ent_type IN %s
        """
        return self.localDb.Query(sql, (entTypes,))

    def __loadFinIndexDatas__(self, creditCode, year, reportType):
        sql = """
            SELECT
                credit_code,index_code,index_value
            FROM
                fin_index
            WHERE 
                credit_code = %s
                AND `year` = %s
                and report_type = %s    
        """
        return self.localDb.Query(sql, (creditCode, year, reportType))

    def __loadSettings__(self):
        """
        加载预警设定
        """
        sql = """
            SELECT * FROM fin_early_warning_settings
            """
        return self.localDb.Query(sql)

    def __loadQuantile__(self):
        """
        加载分位值
        """
        sql = """
            SELECT 
                industry_code, 
                index_code, 
                CAST(tf as DECIMAL(24,6)) as tf,
                CAST(fi as DECIMAL(24,6)) as fi,
                CAST(sf as DECIMAL(24,6)) as sf
            FROM 
                fin_index_quantile
            """
        return self.localDb.Query(sql)

    def __update__(self, creditCode, updateDatas):
        # 先删
        sqlDel = """
        DELETE FROM fin_early_warning WHERE credit_code=%s;
        """
        self.localDb.ExecNoneQuery(sqlDel, (creditCode,))

        # 后插
        sqlInsert = """
        INSERT INTO fin_early_warning
        (credit_code,index_code,index_value,rank_name,hit,report_year,report_type)
        VALUES(%(credit_code)s,%(index_code)s,%(index_value)s,%(rank_name)s,%(hit)s,%(report_year)s,%(report_type)s);
        """
        self.localDb.ExecMany(sqlInsert, updateDatas)

    def __calc__(self, ent: dict, allSettings: dict, quantileDatas: dict):
        """
        计算单个企业的财务指标预警
        """

        # 提取当前企业适适配的指标
        entType = ent["ent_type"]
        curSettings = allSettings[entType] if entType in allSettings.keys() else {}
        if len(curSettings) == 0:
            return

        creditCode = ent["credit_code"]
        industryCode = ent["industry_code"]

        # 抽取当前企业的最新报告期
        yearReportType = self.__loadLatestReportDate__(creditCode=creditCode)
        if yearReportType is None:
            # self.logger.error(f"没有指标数据 creditCode={creditCode}，无法计算")
            return
        year = yearReportType["year"]
        reportType = yearReportType["report_type"]
        if reportType < 0 or reportType > 3:
            # self.logger.error(f"无效的报告期 creditCode={creditCode} report_type={reportType}，无法计算")
            return

        # 抽取当前企业的指标数据
        finIndexDatas = self.__loadFinIndexDatas__(creditCode=creditCode, year=year, reportType=reportType)
        finIndexDatas = {d["index_code"]: d for d in finIndexDatas}

        updateDatas = []

        # 计算当前企业类型的全部预警指标
        for settings in curSettings:
            indexCode = settings["index_code"]
            updateData = {
                "credit_code": creditCode,
                "index_code": indexCode,
                "report_year": year,
                "report_type": reportType
            }

            # 待计算的预警指标不存在对应的指标数据
            if indexCode not in finIndexDatas.keys():
                updateDatas.append(self.__genWarningData__(baseData=updateData))
                continue

            indexValue = CommonUtils.strToNum(finIndexDatas[indexCode]["index_value"])

            # 指标值无效
            if indexValue is None:
                updateDatas.append(self.__genWarningData__(baseData=updateData))
                continue

            # 分位值指标
            if settings["index_type"] == "quantile":
                # 获取分位值数据
                k = f"{industryCode}:{indexCode}"
                quantitlData = quantileDatas[k] if k in quantileDatas.keys() else None

                # 分位值数据无效
                if quantitlData is None:
                    updateDatas.append(self.__genWarningData__(baseData=updateData))
                    continue

                indexDirection = settings["index_direction"]
                # 计算档位
                rank = self.__calcQuantileRank__(indexValue=indexValue, indexDirection=indexDirection, quantileData=quantitlData)

                hit = 0
                if indexDirection == 'positive' and rank <= settings["warning_value"]:
                    # 正向指标 且 指标分位rank低于预警分位rank，命中
                    hit = 1
                elif indexDirection == 'negative' and rank >= settings["warning_value"]:
                    # 负向指标 且 指标分位rank高于预警分位rank，命中
                    hit = 1
                updateDatas.append(self.__genWarningData__(baseData=updateData, indexValue=indexValue, rankName=self.rankNameMapping[rank], hit=hit))
                continue

            # 数值指标
            if settings["index_type"] == "number":
                hit = 0
                rankName = ""
                if settings["index_direction"] == 'positive' and indexValue < settings["warning_value"]:
                    # 正向指标 且 指标值 低于 预警设定阈值
                    hit = 1
                    rankName = f'<{settings["warning_value"]}'
                elif settings["index_direction"] == 'negative' and indexValue > settings["warning_value"]:
                    # 负向指标 且 指标值 高于 预警设定阈值
                    hit = 1
                    rankName = f'>{settings["warning_value"]}'

                updateDatas.append(self.__genWarningData__(baseData=updateData, indexValue=indexValue, rankName=rankName, hit=hit))
                continue

        # 写库 按企业为单位写库
        self.__update__(creditCode=creditCode, updateDatas=updateDatas)

    @staticmethod
    def __calcQuantileRank__(indexValue, indexDirection, quantileData) -> int:
        """
        计算分位值指标所在档位
        """
        if indexDirection == "positive":
            # 正向指标判定
            if indexValue < quantileData["tf"]:
                # [∞,25）
                return 0
            elif quantileData["tf"] <= indexValue < quantileData["fi"]:
                # [25,50)
                return 1
            elif quantileData["fi"] <= indexValue < quantileData["sf"]:
                # [50,75)
                return 2
            else:
                # [75,∞)
                return 3
        else:
            # 负向指标判定
            if indexValue <= quantileData["tf"]:
                # （∞,25]
                return 0
            elif quantileData["tf"] < indexValue <= quantileData["fi"]:
                # (25,50]
                return 1
            elif quantileData["fi"] < indexValue <= quantileData["sf"]:
                # (50,75]
                return 2
            else:
                # (75,∞)
                return 3

    def __genWarningData__(self, baseData, indexValue=None, rankName=None, hit=0):

        baseData["index_value"] = indexValue
        baseData["rank_name"] = rankName
        baseData["hit"] = hit

        # 有效数据
        if indexValue is not None:
            if baseData["index_code"] in self.unitConvertCodes:
                # 单位 转换 元 -> 亿元
                indexValue = indexValue / 100000000

            # 格式化 千分位，2位小数
            baseData["index_value"] = f"{round(indexValue, 3):,.2f}"
        return baseData

    def run(self):
        """
        计算财务预警
        """

        # 加载预警指标设定
        self.logger.info("加载预警指标设定")
        settingsBase = self.__loadSettings__()

        allSettings = {}
        for entType in self.entTypes:
            allSettings[entType] = [d for d in settingsBase if entType in d.keys() and d[entType] == 1]
            self.logger.info(f"加载预警指标设定 {entType}:{len(allSettings[entType])} 条")

        # 加载分位值
        self.logger.info("加载分位值")
        quantileDatas = self.__loadQuantile__()
        quantileDatas = {f"{d['industry_code']}:{d['index_code']}": d for d in quantileDatas}
        self.logger.info(f"加载分位值 {len(quantileDatas)} 条")

        # 加载企业列表数据
        self.logger.info("加载企业")
        enterpriseList = self.__loadEnterprise__(entTypes=self.entTypes)
        self.logger.info(f"加载企业 {len(enterpriseList)} 条")

        # 循环处理企业
        for ent in tqdm(enterpriseList, desc="processing", position=0, file=sys.stdout):
            self.__calc__(ent=ent, allSettings=allSettings, quantileDatas=quantileDatas)


def __main__():
    cleanScreen()

    logger = Logger()
    localDb = ProdDb(logSql=False, autocommit=True)

    t = time.perf_counter()
    logger.info("财务预警计算 开始===================================")

    calc = CalEarlyWarning(logger=logger, localDb=localDb)
    calc.run()

    cast = (time.perf_counter() - t)
    m = math.floor(cast / 60)
    s = cast - m * 60
    logger.info('财务预警计算 %s 总耗时: %d分%.3f秒' % (datetime.datetime.now(), m, s))
    logger.info("财务预警计算 结束===================================")


# 程序入口
if __name__ == "__main__":
    __main__()
